/**
 * The MIT License
 * <p>
 * Copyright (c) 2017
 */
package com.seelyn.flow.workflow;

import com.seelyn.flow.work.NoOpWork;
import com.seelyn.flow.work.Work;
import com.seelyn.flow.work.WorkReport;
import com.seelyn.flow.work.WorkReportPredicate;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A conditional flow is defined by 4 artifacts:
 * <p>
 * <ul>
 * <li>The work to execute first</li>
 * <li>A predicate for the conditional logic</li>
 * <li>The work to execute if the predicate is satisfied</li>
 * <li>The work to execute if the predicate is not satisfied (optional)</li>
 * </ul>
 *
 * @author slacrey  (237242115@qq.com)
 * @see MultiConditionalFlow.Builder
 */
public final class MultiConditionalFlow extends AbstractWorkFlow {

    private final Work toExecute;
    private final Map<WorkReportPredicate, Work> toNextOnPredicateMap;

    MultiConditionalFlow(final String name, final Work toExecute,
                         final Map<WorkReportPredicate, Work> toNextOnPredicateMap) {
        super(name);
        this.toExecute = toExecute;
        this.toNextOnPredicateMap = toNextOnPredicateMap;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public WorkReport call() {
        toExecute.setParams(getParams());
        WorkReport jobReport = toExecute.call();
        for (Map.Entry<WorkReportPredicate, Work> entry : toNextOnPredicateMap.entrySet()) {
            if (entry.getKey().apply(jobReport)) {
                jobReport = entry.getValue().call();
                break;
            }
        }
        return jobReport;
    }

    public static class Builder {

        private String name;
        private Work toExecute;
        private final Map<WorkReportPredicate, Work> toNextOnPredicateMap;
        private WorkReportPredicate tempPredicate;

        private Builder() {
            this.name = UUID.randomUUID().toString();
            this.toExecute = new NoOpWork();
            this.toNextOnPredicateMap = new ConcurrentHashMap<>();
        }

        public static MultiConditionalFlow.Builder aNewMultiConditionalFlow() {
            return new MultiConditionalFlow.Builder();
        }

        public MultiConditionalFlow.Builder named(String name) {
            this.name = name;
            return this;
        }

        public MultiConditionalFlow.Builder execute(Work work) {
            this.toExecute = work;
            return this;
        }

        public MultiConditionalFlow.Builder when(WorkReportPredicate predicate) {
            this.tempPredicate = predicate;
            return this;
        }

        public MultiConditionalFlow.Builder then(Work work) {
            this.toNextOnPredicateMap.put(tempPredicate, work);
            this.tempPredicate = null;
            return this;
        }

        public MultiConditionalFlow build() throws Exception {

            if (this.tempPredicate != null) {
                throw new Exception("sad");
            }
            return new MultiConditionalFlow(name, toExecute, toNextOnPredicateMap);
        }
    }
}
